#include <iostream>
#include <pthread.h>
#include <time.h>
#include <string>
#include "request.h"
#include "monitor.h"
#include "logger.h"
using namespace std;

#define NUM_CLIENTS 1
#define NUM_WORKERS 1
#define NUM_SCHEDULERS 1 // NUM_SCHEDULERS should always stay at 1
#define RUNTIME 1

// MACROS for Worker threads---------------------------------
#define STARTING_TRACK 4
// Typically, average desktop disk seek time around 9000 microseconds
#define DISK_MOVEMENT_OVERHEAD 3000
#define DATA_ACCESS_OVERHEAD 10
// END MACROS for Worker threads-----------------------------
//-----------------------------------------------------------
// MACROS for Client threads---------------------------------
#define MAX_WORK 100
// END MACROS for Client threads-----------------------------

// Declare the shared logger to be used for logging and metrics
logger * shared_logger;

// Declare shared monitor object
monitor * shared_mon;

// Condition variable for starting the worker 
pthread_cond_t shared_signal_worker;
pthread_mutex_t shared_signal_worker_mutex;

// create struct to pass threadid and shared_monitor pointer
typedef struct threadInput {
	// The ThreadID that has been assigned to the thread
	int tid;
	// The shared logger object
	logger *log;
	// The shared monitor object
	monitor *mon;
	// Condition variable - main waits for this to be called at least
	// once before it starts the worker. Otherwise the worker will
	// start up and immediately quit. This causes problems.
	pthread_cond_t *signal_worker;
	// Debug level - set by the -debug commandline parameter
	char *debuglevel;
}  threadInput;

// -----------------------------------------------------------------
// START Thread Subroutines
// -----------------------------------------------------------------
void *client(void *arg) {
	// Extract shared data
	threadInput *data = (threadInput*)arg;
	// For generating debug strings
	char buffer[100];

	// Integer for keeping track of how many requests this client submits and recieves back completed
	int completedRequests = 0;

	int disk_track = 0;
	int disk_work = 1;
	request* clientReq = new request(0, 1, data->tid);
	bool req_passed = false;
	
	// Initialize client runtime calculation variables
	time_t start_time, cur_time;
	double run_time = RUNTIME;
	start_time = time(NULL);
	cur_time = time(NULL);

	// Increment client counter since this is now a new live client
	data->mon->incLiveClients();
	pthread_cond_signal(data->signal_worker);
	// Debug statementschar buffer[100];
	sprintf(buffer, "Increment liveClients... %d", data->mon->getLiveClients());
	data->log->debug(buffer, "a");
	buffer[0] = 0;

	while(difftime(cur_time, start_time) < run_time) {
		req_passed = false;
		//initialize random seed
		srand (clock());

		//generate random values for the track number and amount of work to be done.
		//disk_track is in the range [0, 15], and disk_work is in the range [1, MAX_WORK]
		disk_track = rand() % 16;
		disk_work = rand() % MAX_WORK + 1;

		clientReq->setTrackNum(disk_track);
		clientReq->setWorkSize(disk_work);
		//send the request to the monitor for processing
		req_passed = data->mon->addRequest(clientReq);
		if (req_passed) {
			completedRequests ++;
		}
		sprintf(buffer, "Disk track is %d, Disk work is %d", disk_track, disk_work);
		data->log->debug(buffer, "c");
		buffer[0] = 0;
		//cout << "Disk track is " << disk_track << ", Disk work is " << disk_work << endl;
		
		//result = request.getOutputData();
		
		cur_time = time(NULL);
	}
	
	// Decrement client counter since this client is dead and exiting
	data->mon->decLiveClients();
	
	pthread_cond_signal(&(data->mon->schedWait));
	// Debug statement to state that client has finished
	sprintf(buffer, "Client %d has finished and recieved %d completed requests.\nliveClients=%d", data->tid, completedRequests, data->mon->getLiveClients());
	data->log->debug(buffer, "a");
	buffer[0] = 0;
	
	// Client destroys pthread object
	pthread_exit(NULL);
}

void *worker(void *arg) {
	// Extract shared data
	threadInput *data = (threadInput*)arg;
   
	int new_track, work_size, work_overhead, disk_head_movement;
	int previous_track = STARTING_TRACK;

	// This loop executes while there is either still work to do
	// or there are still clients generating requests
	while (data->mon->getLiveClients() > 0 ){//|| data->mon->isWorkLeft()) {
		request* currentReq = data->mon->removeRequest();
		if (currentReq != NULL) {
			//retrieves the relevant request data
			new_track = currentReq->getTrackNum();
			work_size = currentReq->getWorkSize();
		   
			//calculates the distance the disk head has to travel to service the request
			disk_head_movement = abs(new_track - previous_track);

			//calculates a length of time "required" to access the requested data from the disk
			work_overhead = (DISK_MOVEMENT_OVERHEAD * disk_head_movement) + (DATA_ACCESS_OVERHEAD * work_size);

			//freezes the thread to simulate the I/O request
			usleep(work_overhead);

			//sends results of the service back to the client
			//(to be updated when we decide what we want to give to the client)
			//currentReq->setOutputData( (char) work_overhead);
		   
			//cout << "The work has been finished in " << work_overhead << " ms." << endl;

			//updates the location of the disk head
			previous_track = new_track;
		
			// Signal to the client that the work has been finished
			data->mon->signalClient(currentReq);
		} else {}
	}
	data->log->debug("Worker has finished!", "a");// << endl;
   
	pthread_exit(NULL);
}

void *scheduler(void *arg){
	threadInput *data = (threadInput*)arg;
	while(data->mon->getLiveClients() > 0 ){//}|| data->mon->isWorkLeft()) {
		//if the monitor's input queue is empty, or output full, put the thread to sleep
		data->mon->scheduleRequests();
	}
	pthread_cond_signal(&(data->mon->inputCheck));
	data->log->debug("Scheduler has finished!", "a");// << endl;
	pthread_exit(NULL);
}

// -----------------------------------------------------------------
// END Thread Subroutines
// -----------------------------------------------------------------

// create main function which creates monitor and spawns client, worker, scheduler
int main(int argc, char**argv) {
	// For generating debug statements
	char buffer[100];
	/*
		Take and process commandline arguments
		-algo		Specifies which algorithm to run
			s	Shortest seek time first
			e	Elevator
		-debug		Generates a log file with debug statments 
					depeding on the following option debugging is
					off by default if not specified with this
					commandline flag
			a	High priority statments
			b	Medium and High priority statements (includes a)
			c	All general debug statements (includes a and b)
				NOTE: c is most verbose
	*/
	/*
			Defalut parameters (if not specified via commandline):
				algo: Shortest seek time first
				debug: High priority statements
	*/
	char *algo, *debug;
	char temp1, temp2;
	temp1 = 's';
	algo = &temp1;
	temp2 = 'a';
	debug = &temp2;
	for (int i = 1; i < argc; i++) {
		// Retrieve command line parameters
		if (i+1 != argc) {
			if (strcmp(argv[i],"-algo")==0) {
				// Only set algo if valid parameter
				if (strcmp(argv[i+1],"e")==0) {
					algo = argv[i+1];
				}
			} else if (strcmp(argv[i],"-debug")==0) {
				// Only set debug if valid parameter
				if ((strcmp(argv[i+1],"a")==0) || 
					(strcmp(argv[i+1],"b")==0) || 
					(strcmp(argv[i+1],"c")==0)) {
					debug = argv[i+1];
					// DEBUG STATEMENT
				}
			}
		} else if (argv[i][0] == '-') {
			// DEBUG STATEMENT UNRECOGNIZED ARG OR MISSING PARAMETER AFTER..
		} else {
			// DEBUG STATEMENT UNRECOGNIZED ARG
		}
	}
	
	// Initialize shared resources
	string logfile = "logging.log";
	shared_logger = new logger(&logfile[0], debug);
	shared_mon = new monitor(shared_logger);
	pthread_cond_init(&shared_signal_worker, NULL);
	pthread_mutex_init(&shared_signal_worker_mutex, NULL);
		
	// Declare int for determining if a pthread_create call succeeded
	int rc;
	
	// spawn clients
	pthread_t clients[NUM_CLIENTS];
	threadInput clients_data[NUM_CLIENTS];
	
	for (int t = 0; t < NUM_CLIENTS; t++) {
		sprintf(buffer, "In main: creating client %d", t);
		shared_logger->debug(buffer, "a");
		buffer[0] = 0;
		//cout << "In main: creating client " << t << endl;
		// Initialize client data for current client
			clients_data[t].tid = t;
			clients_data[t].log = shared_logger;
			clients_data[t].mon = shared_mon;
			clients_data[t].signal_worker = &shared_signal_worker;
			clients_data[t].debuglevel = debug;
		// Fork the current client thread
			rc = pthread_create(&clients[t], NULL, client, (void *)&clients_data[t]);
			if (rc) {
				cout << "Error: pthread_create, rc: " << rc << endl;
				return EXIT_FAILURE;
			}
	}

	// spawn workers
	pthread_t workers[NUM_WORKERS];
	threadInput workers_data[NUM_WORKERS];
	pthread_mutex_lock(&shared_signal_worker_mutex);
	pthread_cond_wait(&shared_signal_worker, &shared_signal_worker_mutex);
	pthread_mutex_unlock(&shared_signal_worker_mutex);
	for (int t = 0; t < NUM_WORKERS; t++) {
		sprintf(buffer, "In main: creating worker %d", t);
		shared_logger->debug(buffer, "a");
		buffer[0] = 0;
		//cout << "In main: creating worker " << t << endl;
		// Initialize worker data for current worker
			workers_data[t].tid = t;
			workers_data[t].log = shared_logger;
			workers_data[t].mon = shared_mon;
			workers_data[t].debuglevel = debug;
		// Fork the current worker thread
			rc = pthread_create(&workers[t], NULL, worker, (void *)&workers_data[t]);
			if (rc) {
				cout << "Error: pthread_create, rc: " << rc << endl;
				return EXIT_FAILURE;
			}
	}
	
	
	// spawn scheduler
	pthread_t schedulers[NUM_SCHEDULERS];
	threadInput schedulers_data[NUM_SCHEDULERS];
	
	for (int t = 0; t < NUM_SCHEDULERS; t++) {
		sprintf(buffer, "In main: creating scheduler %d", t);
		shared_logger->debug(buffer, "a");
		buffer[0] = 0;
		//cout << "In main: creating scheduler " << t << endl;
		// Initialize sheduler data for current scheduler
			schedulers_data[t].tid = t;
			schedulers_data[t].log = shared_logger;
			schedulers_data[t].mon = shared_mon;
			schedulers_data[t].debuglevel = debug;
		// Fork the current scheduler thread
			rc = pthread_create(&schedulers[t], NULL, scheduler, (void *)&schedulers_data[t]);
			if (rc) {
				cout << "Error: pthread_create, rc: " << rc << endl;
				return EXIT_FAILURE;
			}
	}
	
	
	// Block main until all clients complete
	for (int i = 0; i < NUM_CLIENTS; i++) {
		pthread_join(clients[i],NULL);
	}
	
	// Block main until all workers complete
	for (int i = 0; i < NUM_WORKERS; i++) {
		pthread_join(workers[i],NULL);
	}
	
	// Block main until all schedulers complete
	for (int i = 0; i < NUM_SCHEDULERS; i++) {
		pthread_join(schedulers[i],NULL);
	}
	
	// Print out a final statement
	sprintf(buffer, "ALL WORK FINISHED! REQDIFF=%d  REQPROC=%d", shared_mon->getReqDiff(), shared_mon->getReqProc());
	shared_logger->debug(buffer, "a");
	buffer[0] = 0;
	//cout << "ALL WORK FINISHED! REQDIFF=" << shared_mon->getReqDiff() << " REQPROC=" << shared_mon->getReqProc() << endl;
	
	// Cleanup
	delete shared_logger;
	delete shared_mon;
	pthread_cond_destroy(&shared_signal_worker);
	pthread_mutex_destroy(&shared_signal_worker_mutex);
	// delete client arrays
	// delete worker arrays
	// delete sheduler arrays
	
	return 0;
}
